﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Spi;
using System.Reflection;

namespace NGuice.Inject.Internal
{
    //complete！
    /// <summary>
    /// C#中没有泛型通配符，使用此标记接口解决此问题。但实际的方法可能需要通过反射调用
    /// </summary>
    internal interface SingleParameterInjector
    {
    }

    /// <summary>
    /// 解析一个单独的参数，用于构造器和方法调用上
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal sealed class SingleParameterInjector<T> : SingleParameterInjector
    {
        private static readonly object[] NO_ARGUMENTS = { };

        private readonly Dependency<T> dependency;
        private readonly InternalFactory<T> factory;

        internal SingleParameterInjector(Dependency<T> dependency, InternalFactory<T> factory)
        {
            this.dependency = dependency;
            this.factory = factory;
        }

        private T Inject(Errors errors, InternalContext context)
        {
            Dependency previous = context.SetDependency(dependency);
            try
            {
                return factory.Get(errors.WithSource(dependency), context, dependency, false);
            }
            finally
            {
                context.SetDependency(previous);
            }
        }

        /// <summary>
        /// 返回一个参数值的数组
        /// </summary>
        /// <param name="errors"></param>
        /// <param name="context"></param>
        /// <param name="parameterInjectors">必须具有泛型参数</param>
        /// <returns></returns>
        internal static object[] GetAll(Errors errors, InternalContext context, SingleParameterInjector[] parameterInjectors)
        {
            if (parameterInjectors == null)
            {
                return NO_ARGUMENTS;
            }

            int numErrorsBefore = errors.Size;

            int size = parameterInjectors.Length;
            object[] parameters = new object[size];

            // optimization: use manual for/each to save allocating an iterator here  
            for (int i = 0; i < size; i++)
            {
                SingleParameterInjector parameterInjector = parameterInjectors[i];
                try
                {
                    MethodInfo method = parameterInjector.GetType().GetMethod("Inject", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Errors), typeof(InternalContext) }, null);
                    parameters[i] = method.Invoke(parameterInjector, new object[] { errors, context });
                }
                catch (ErrorsException e)
                {
                    errors.Merge(e.Errors);
                }
                catch (Exception e)
                {
                    //反射异常
                    throw new ArgumentException("refactor invoke Inject method error", e);
                }
            }

            errors.ThrowIfNewErrors(numErrorsBefore);
            return parameters;
        }
    }
}
